package com.linking.third.party.manager;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.borealis.common.utils.DataUtils;
import com.google.common.collect.Maps;
import com.linking.third.party.pojo.bo.MidasPayBO;
import com.linking.third.party.pojo.bo.MidasPayResBO;
import java.math.BigDecimal;
import java.util.Map;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

/**
 * @Author YaoWeiXin
 * @Date 2020/10/21 11:17
 * @Description 米大师 管理类
 */
@Slf4j
public class MidasManager {

  private final static String TEST_BALANCE_URL = "https://api.weixin.qq.com/cgi-bin/midas/sandbox/getbalance?access_token=ACCESS_TOKEN";
  private final static String PROD_BALANCE_URL = "https://api.weixin.qq.com/cgi-bin/midas/getbalance?access_token=ACCESS_TOKEN";

  private final static String TEST_TRADE_URL = "https://api.weixin.qq.com/cgi-bin/midas/sandbox/pay?access_token=ACCESS_TOKEN";
  private final static String PROD_TRADE_URL = "https://api.weixin.qq.com/cgi-bin/midas/pay?access_token=ACCESS_TOKEN";

  public static BigDecimal getBalance(MidasPayBO midasPay) {
    boolean sendBox = midasPay.getSendBox();
    boolean isLog = midasPay.getIsLog();
    String url = sendBox ? TEST_BALANCE_URL : PROD_BALANCE_URL;
    url = url.replaceAll("ACCESS_TOKEN", midasPay.getAccessToken());
    String appId = midasPay.getAppId();
    String openid = midasPay.getOpenid();
    String offerId = midasPay.getOfferId();
    String serverId = midasPay.getZoneId();
    String pf = midasPay.getPf();
    BigDecimal amount = midasPay.getAmount();

    Map<String, Object> params = Maps.newHashMap();
    params.put("openid", openid);
    params.put("appid", appId);
    params.put("offer_id", offerId);
    params.put("ts", DateUtil.currentSeconds());
    params.put("zone_id", serverId);
    params.put("pf", pf);

    String sign = signKeyValue(params, midasPay.getAppSecret(), sendBox);
    if (sendBox || isLog) {
      log.debug("params====================={}", JSON.toJSONString(params));
    }
    params.put("sig", sign);
    if (sendBox || isLog) {
      log.debug("sig url====================={}", url);
      log.debug("sig params====================={}", JSON.toJSONString(params));
    }
    String response = doPayRequest(url, params);
    if (sendBox || isLog) {
      log.debug("response====================={}", response);
    }
    if (StrUtil.isEmpty(response)) {
      return null;
    }
    JSONObject data = JSON.parseObject(response);
    String returnCode = "errcode";
    int code = data.getInteger(returnCode);
    if (code == 0) {
      return data.getBigDecimal("balance");
    } else {
      String msg = data.getString("errmsg");
      log.debug("midas getBalance==========={},{}", code, msg);
      return null;
    }
  }

  /**
   * 创建支付
   *
   * @param midasPay 米大师支付参数
   * @return 创建结果
   */
  public static MidasPayResBO doPay(MidasPayBO midasPay) {
//    BigDecimal balance = getBalance(midasPay);
//    if (balance == null) {
//      return MidasPayResBO.ofFail(-1, "getBalance error");
//    }
//    if (balance.compareTo(midasPay.getAmount()) < 0) {
//      return MidasPayResBO.ofFail(90013, "balance not enough");
//    }
    boolean sendBox = midasPay.getSendBox();
    boolean isLog = midasPay.getIsLog();
    String url = sendBox ? TEST_TRADE_URL : PROD_TRADE_URL;
    url = url.replaceAll("ACCESS_TOKEN", midasPay.getAccessToken());
    String appId = midasPay.getAppId();
    String openid = midasPay.getOpenid();
    String offerId = midasPay.getOfferId();
    String serverId = midasPay.getZoneId();
    String pf = midasPay.getPf();
    String userIp = midasPay.getUserIp();
    BigDecimal amount = midasPay.getAmount();
    String orderId = midasPay.getOrderId();
    String payItem = midasPay.getProductDesc();
    String remark = midasPay.getRemark();

    Map<String, Object> params = Maps.newHashMap();
    params.put("openid", openid);
    params.put("appid", appId);
    params.put("offer_id", offerId);
    params.put("ts", DateUtil.currentSeconds());
    params.put("zone_id", serverId);
    params.put("pf", pf);
    params.put("amt", amount);
    params.put("bill_no", orderId);
    params.put("pay_item", payItem);

    String sign = signKeyValue(params, midasPay.getAppSecret(), sendBox);
    if (sendBox || isLog) {
      log.debug("params====================={}", JSON.toJSONString(params));
    }
    params.put("sig", sign);
    if (sendBox || isLog) {
      log.debug("sig url====================={}", url);
      log.debug("sig params====================={}", JSON.toJSONString(params));
    }
    int size = 5;
    String msg = "system error";
    for (int i = 0; i < size; i++) {
      String response = doPayRequest(url, params);
      if (sendBox || isLog) {
        log.debug("response====================={},{}", i, response);
      }
      if (StrUtil.isEmpty(response)) {
        return MidasPayResBO.ofFail(-2, "response is empty");
      }
      JSONObject data = JSON.parseObject(response);
      String returnCode = "errcode";
      String errMsg = "errmsg";
      int code = data.getInteger(returnCode);
      switch (code) {
        case 0:
          return MidasPayResBO.of(data.getString("billNo"));
        case -1:
          msg = data.getString(errMsg);
          log.debug("midas do pay fail -1==========={},{}", code, msg);
          continue;
        default:
          msg = data.getString(errMsg);
          log.debug("midas do pay fail==========={},{}", code, msg);
          return MidasPayResBO.ofFail(code, data.getString(errMsg));
      }
    }
    return MidasPayResBO.ofFail(-1, msg);
  }

  private static String doPayRequest(String url, Map<String, Object> params) {
    return HttpUtil.post(url, JSON.toJSONString(params), 5000);
  }

  /**
   * 将MAP的参数进行签名
   *
   * @param map     参数集合
   * @param signKey 签名KEY
   * @return 签名字符串
   */
  public static String signKeyValue(Map<String, Object> map, String signKey, boolean sendBox) {
    map = DataUtils.sortMapByKey(map);
    StringBuilder str = new StringBuilder();
    if (map != null) {
      for (String key : map.keySet()) {
        if ("sign".equals(key)) {
          continue;
        }
        String value = Convert.toStr(map.get(key));
        if (StringUtils.isEmpty(value)) {
          continue;
        }
        str.append(key);
        str.append("=");
        str.append(value);
        str.append("&");
      }
    }
    str.append("org_loc=");
    str.append(sendBox ? "/cgi-bin/midas/sandbox/pay" : "/cgi-bin/midas/pay");
    str.append("&method=POST&secret=");
    str.append(signKey);
    if (sendBox) {
      log.info("signKeyValue param=============={}", str.toString());
    }
    String sign = hmacSha256(str.toString(), signKey);
    if (sendBox) {
      log.info("signKeyValue sign=============={}", sign);
    }
    return sign;
  }

  /**
   * 将加密后的字节数组转换成字符串
   *
   * @param b 字节数组
   * @return 字符串
   */
  public static String byteArrayToHexString(byte[] b) {
    StringBuilder hs = new StringBuilder();
    String tmp;
    for (int n = 0; b != null && n < b.length; n++) {
      tmp = Integer.toHexString(b[n] & 0XFF);
      if (tmp.length() == 1) {
        hs.append('0');
      }
      hs.append(tmp);
    }
    return hs.toString().toLowerCase();
  }

  /**
   * sha256_HMAC加密
   *
   * @param message 消息
   * @param secret  秘钥
   * @return 加密后字符串
   */
  public static String hmacSha256(String message, String secret) {
    String hash = "";
    try {
      Mac hmacSha256 = Mac.getInstance("HmacSHA256");
      SecretKeySpec secretKey = new SecretKeySpec(secret.getBytes(), "HmacSHA256");
      hmacSha256.init(secretKey);
      byte[] bytes = hmacSha256.doFinal(message.getBytes());
      hash = byteArrayToHexString(bytes);
    } catch (Exception e) {
      log.error("Error HmacSHA256 ===========: ", e);
    }
    return hash;
  }
}
